package com.ljf.mysql.config;

import com.ljf.framework.security.LjfSecurityManager;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * mybatis 拦截器
 *
 * 避免在使用xml中的sql时，mp不能自动注入属性
 */
@Intercepts(value = {@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class AutoSetInterceptor implements Interceptor {

    private static final String SET = " SET ";
    private static final String CREATE_TIME = "createTime";
    private static final String UPDATE_TIME = "updateTime";
    private static final String UPDATE_TIME_DB = "update_time";
    private static final String CREATE_BY = "createBy";
    private static final String UPDATE_BY = "updateBy";
    private static final String UPDATE_BY_DB = "update_by";

    @Override
    public Object plugin(Object target) {
        //判断是否拦截这个类型对象（根据@Intercepts注解决定），然后决定是返回一个代理对象还是返回原对象。
        //故我们在实现plugin方法时，要判断一下目标类型，如果是插件要拦截的对象时才执行Plugin.wrap方法，否则的话，直接返回目标本身。
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        Object obj = boundSql.getParameterObject();
        String sql = boundSql.getSql();
        // sql 语句格式化 此时参数值还是 ？ 占位 \\s+ 表示一个或多个空格
        sql = sql.replaceAll("\r", " ").replaceAll("\\s+", " ").trim().toUpperCase();

        if (sql.startsWith("INSERT")) {

            setAllFieldValue(obj, CREATE_TIME, new Date());
            setAllFieldValue(obj, UPDATE_TIME, new Date());
            setAllFieldValue(obj, CREATE_BY, LjfSecurityManager.getUsername());
            setAllFieldValue(obj, UPDATE_BY, LjfSecurityManager.getUsername());

            setFieldValue(boundSql, "parameterObject", obj);

        } else if (sql.startsWith("UPDATE")) {
            String sqlOld = sql;
            if (existField(obj,UPDATE_TIME)) {
                sql = sql.replace(SET, SET + UPDATE_TIME_DB + " = NOW(), ");
            }

            if (existField(obj,UPDATE_BY)){
                String username = LjfSecurityManager.getUsername();
                sql = sql.replace(SET, SET + UPDATE_BY_DB + " = " + username + ", ");
            }

            if (!(sqlOld.equals(sql))) {
                setFieldValue(boundSql, "sql", sql);
            }

        }

        return invocation.proceed();

    }


    /**
     * 判断参数obj是否存在fieldName属性
     *
     * @param obj        目标对象
     * @param fieldName  目标属性
     */
    private boolean existField(Object obj, String fieldName) throws IllegalAccessException {
        Field field = null;
        if (obj instanceof List) {
            for (Object obj2 : (List) obj) {
                boolean b = existField(obj2, fieldName);
                if(b) return true;
            }
        }

        if (obj instanceof Map) {
            for (Object obj2 : ((Map) obj).values()){
                boolean b = existField(obj2, fieldName);
                if(b) return true;
            }
        }
        field = getField(obj, fieldName);
        return field != null;
    }
    /**
     * 利用反射设置指定对象中的所有指定属性为指定的值
     *
     * @param obj        目标对象
     * @param fieldName  目标属性
     * @param fieldValue 目标值
     */
    private void setAllFieldValue(Object obj, String fieldName, Object fieldValue) throws IllegalAccessException {
        Field field = null;
        if (obj instanceof List) {
            for (Object obj2 : (List) obj) {
                setAllFieldValue(obj2, fieldName, fieldValue);
            }
            return;
        }

        if (obj instanceof Map) {
            ((Map) obj).forEach( (k,v) -> {
                try {
                    setAllFieldValue(v, fieldName, fieldValue);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
            return;
        }
        setFieldValue(obj, fieldName, fieldValue);
    }

    /**
     * 利用反射设置指定对象的指定属性为指定的值
     *
     * @param obj        目标对象
     * @param fieldName  目标属性
     * @param fieldValue 目标值
     */
    private void setFieldValue(Object obj, String fieldName, Object fieldValue) throws IllegalAccessException {
        Field field = getField(obj, fieldName);
        if (field != null) {
            field.setAccessible(true);
            field.set(obj, fieldValue);
        }
    }

    /**
     * 利用反射获取指定对象的指定属性
     *
     * @param obj       目标对象
     * @param fieldName 目标属性
     * @return 目标字段
     */
    private static Field getField(Object obj, String fieldName) {
        if (obj == null) return null;
        Field field = null;
        for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {

                field = clazz.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
                //这里不用做处理，子类没有该字段，可能父类有，都没有就返回null
            }
        }
        return field;
    }

}
